/*
 *  tracker/carbon/PreferencesDialog.cpp
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 *  PreferencesDialog.cpp
 *  MilkyTracker
 *
 *  Created by Peter Barth on 07.04.06.
 *
 */

#include "PreferencesDialog.h"
#include "Carbon_Definitions.h"
#include "TrackerSettingsDatabase.h"
#include "RtMidi.h"
#include "RtError.h"

#define KEY_USEMIDI "USEMIDI"
#define KEY_RECORDVELOCITY "RECORDVELOCITY"
#define KEY_VELOCITYAMPLIFY "VELOCITYAMPLIFY"
#define KEY_SAVEPREFS "SAVEPREFS"
#define KEY_MIDIDEVICE "MIDIDEVICE"
#define KEY_INSERTEMULATION "insertemulation"
#define KEY_SIXTEENBITCOLOR "sixteenbitcolor"

static CFStringRef applicationID = CFSTR("com.Titan.MilkyTracker");

pascal OSStatus PreferencesDialog::WindowEventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData)
{
    OSStatus result = eventNotHandledErr;
    UInt32 eventClass, eventKind;

    eventClass = GetEventClass(event);
    eventKind = GetEventKind(event);

    PreferencesDialog* prefDlg = reinterpret_cast<PreferencesDialog*>(userData);

    switch (eventClass)
    {
        case kEventClassControl:
        {
            ControlRef targetControl = NULL;
            ControlID targetControlID;

            GetEventParameter(event, kEventParamDirectObject, typeControlRef,
                              NULL, sizeof(targetControl), NULL, &targetControl);
            if (targetControl)
                GetControlID(targetControl, &targetControlID);

            switch (eventKind)
            {
                case kEventControlHit:
                {
                    switch (targetControlID.id)
                    {
                        // simulate insert key with ctrl+up
                        case 131:
                            prefDlg->setFakeInsertKey(GetControl32BitValue(targetControl) - 1);
                            result = noErr;
                            break;

                        // 15 bit color toggle
                        case 132:
                            prefDlg->toggleUse15BitColorDepth();
                            result = noErr;
                            break;

                        // Enable MIDI device
                        case 129:
                            prefDlg->toggleUseMidiDevice();
                            result = noErr;
                            break;

                        // Save MIDI preferences
                        case 128:
                            prefDlg->toggleSavePreferences();
                            result = noErr;
                            break;

                        // Record key velocity
                        case 127:
                            prefDlg->toggleRecordVelocity();
                            result = noErr;
                            break;

                        // velocity amplify slider
                        case 125:
                        {
                            prefDlg->storeVelocityAmplify(GetControlValue(targetControl));
                            result = noErr;
                            break;
                        }
                    }

                    break;
                }
            }

            break;
        }

        // Handle window close event (= discard)
        case kEventClassWindow:
        {
            switch (eventKind)
            {
                case kEventWindowClose:
                {
                    prefDlg->restoreDataBase();
                    prefDlg->hide();
                    result = noErr;
                    break;
                }
            }

            break;
        }

        case kEventClassCommand:
        {
            switch (eventKind)
            {
                case kEventCommandProcess:
                {
                    HICommand command;
                    GetEventParameter(event, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &command);

                    switch (command.commandID)
                    {
                        case kDiscardPreferences:
                            prefDlg->restoreDataBase();

                        case kConfirmPreferences:
                        {
                            // These two events are better off when sent to the main window
                            SendEventToWindow(event, prefDlg->mainWindow);
                            break;
                        }

                        default:
                            // Handle MIDI device selection from pop-up menu
                            if (command.commandID >= kMIDIDeviceBaseCommand &&
                                command.commandID <= kMIDIDeviceBaseCommand + prefDlg->getNumMidiDevices())
                            {
                                prefDlg->storeMidiDeviceName(command.commandID - kMIDIDeviceBaseCommand);
                                result = noErr;
                            }
                    }

                    break;
                }
            }
            break;
        }
    }

    return result;
}

PreferencesDialog::PreferencesDialog (WindowRef windowRef, WindowRef mainWindowRef) :
    preferencesWindow(windowRef),
    mainWindow(mainWindowRef),
    m_dataBase(NULL),
    m_dataBaseCopy(NULL),
    midiin(NULL)
{
    EventTypeSpec eventSpec[] = { { kEventClassCommand, kEventCommandProcess },
                                  { kEventClassControl, kEventControlHit },
                                  { kEventClassWindow, kEventWindowClose } };

    InstallWindowEventHandler(windowRef,
                              NewEventHandlerUPP(WindowEventHandler),
                              sizeof(eventSpec) / sizeof(EventTypeSpec),
                              (EventTypeSpec*)&eventSpec,
                              (void*)this,
                              NULL);

    // Create RtMidi instance for querying device information
    try
    {
        midiin = new RtMidiIn();
    }
    catch (RtError& error)
    {
        error.printMessage();
        midiin = NULL;
    }

    initDataBase();
}

PreferencesDialog::~PreferencesDialog ()
{
    shutdownDataBase();
    delete m_dataBase;
    delete m_dataBaseCopy;
    delete midiin;
}

void PreferencesDialog::show()
{
    backupDataBase();
    initDialog();
    ShowWindow(preferencesWindow);
    SelectWindow(preferencesWindow);
}

void PreferencesDialog::hide()
{
    HideWindow(preferencesWindow);
}

void PreferencesDialog::initDataBase()
{
    m_dataBase = new TrackerSettingsDatabase();

    m_dataBase->store(KEY_USEMIDI, 0);
    m_dataBase->store(KEY_SAVEPREFS, 0);
    m_dataBase->store(KEY_MIDIDEVICE, "");
    m_dataBase->store(KEY_RECORDVELOCITY, 0);
    m_dataBase->store(KEY_VELOCITYAMPLIFY, 100);
    m_dataBase->store(KEY_INSERTEMULATION, 0);
    m_dataBase->store(KEY_SIXTEENBITCOLOR, 0);

    // try to retrieve the values from the PLIST
    Boolean success = FALSE;
    CFStringRef key = CFSTR(KEY_USEMIDI);
    Boolean b = CFPreferencesGetAppBooleanValue(key, applicationID, &success);
    if (success)
        m_dataBase->store(KEY_USEMIDI, b);

    // Return boolean
    success = FALSE;
    key = CFSTR(KEY_SAVEPREFS);
    b = CFPreferencesGetAppBooleanValue(key, applicationID, &success);
    if (success)
        m_dataBase->store(KEY_SAVEPREFS, b);

    // Return string, little bit more complicated
    key = CFSTR(KEY_MIDIDEVICE);
    CFPropertyListRef plistRef = CFPreferencesCopyAppValue(key, applicationID);
    if (plistRef)
    {
        if (CFGetTypeID(plistRef) == CFStringGetTypeID())
        {
            CFStringRef stringRef = static_cast<CFStringRef>(plistRef);
            char buffer[512];
            CFStringGetCString(stringRef, buffer, 512, kCFStringEncodingASCII);
            m_dataBase->store(KEY_MIDIDEVICE, buffer);
        }
    }

    // More boolean values following
    success = FALSE;
    key = CFSTR(KEY_RECORDVELOCITY);
    b = CFPreferencesGetAppBooleanValue(key, applicationID, &success);
    if (success)
        m_dataBase->store(KEY_RECORDVELOCITY, b);

    // Integer value
    success = FALSE;
    key = CFSTR(KEY_VELOCITYAMPLIFY);
    int i = CFPreferencesGetAppIntegerValue(key, applicationID, &success);
    if (success)
        m_dataBase->store(KEY_VELOCITYAMPLIFY, i);

    // Integer value
    success = FALSE;
    key = CFSTR(KEY_INSERTEMULATION);
    i = CFPreferencesGetAppIntegerValue(key, applicationID, &success);
    if (success)
        m_dataBase->store(KEY_INSERTEMULATION, i);

    // More boolean values following
    success = FALSE;
    key = CFSTR(KEY_SIXTEENBITCOLOR);
    b = CFPreferencesGetAppBooleanValue(key, applicationID, &success);
    if (success)
        m_dataBase->store(KEY_SIXTEENBITCOLOR, b);
}

void PreferencesDialog::shutdownDataBase()
{
    CFStringRef yes = CFSTR("yes");
    CFStringRef no = CFSTR("no");

    if (m_dataBase->restore(KEY_SAVEPREFS)->getIntValue())
    {
        CFStringRef key = CFSTR(KEY_USEMIDI);
        CFPreferencesSetAppValue(key, m_dataBase->restore(KEY_USEMIDI)->getBoolValue() ? yes : no, applicationID);

        key = CFSTR(KEY_SAVEPREFS);
        CFPreferencesSetAppValue(key, m_dataBase->restore(KEY_SAVEPREFS)->getBoolValue() ? yes : no, applicationID);

        key = CFSTR(KEY_MIDIDEVICE);
        CFStringRef CFStrDevName = CFStringCreateWithCString(NULL, m_dataBase->restore(KEY_MIDIDEVICE)->getStringValue(), kCFStringEncodingASCII);
        CFPreferencesSetAppValue(key, CFStrDevName, applicationID);
        CFRelease(CFStrDevName);

        key = CFSTR(KEY_RECORDVELOCITY);
        CFPreferencesSetAppValue(key, m_dataBase->restore(KEY_RECORDVELOCITY)->getBoolValue() ? yes : no, applicationID);

        SInt32 number = m_dataBase->restore(KEY_VELOCITYAMPLIFY)->getIntValue();
        CFNumberRef numberRef = CFNumberCreate(NULL, kCFNumberSInt32Type, &number);
        key = CFSTR(KEY_VELOCITYAMPLIFY);
        CFPreferencesSetAppValue(key, numberRef, applicationID);
        CFRelease(numberRef);
    }
    else
    {
        // remove keys from preferences (or something like that if it's possible)
        CFStringRef key = CFSTR(KEY_USEMIDI);
        CFPreferencesSetAppValue(key, NULL, applicationID);

        key = CFSTR(KEY_SAVEPREFS);
        CFPreferencesSetAppValue(key, NULL, applicationID);

        key = CFSTR(KEY_MIDIDEVICE);
        CFPreferencesSetAppValue(key, NULL, applicationID);

        key = CFSTR(KEY_RECORDVELOCITY);
        CFPreferencesSetAppValue(key, NULL, applicationID);

        key = CFSTR(KEY_VELOCITYAMPLIFY);
        CFPreferencesSetAppValue(key, NULL, applicationID);
    }

    SInt32 number = m_dataBase->restore(KEY_INSERTEMULATION)->getIntValue();
    CFNumberRef numberRef = CFNumberCreate(NULL, kCFNumberSInt32Type, &number);
    CFStringRef key = CFSTR(KEY_INSERTEMULATION);
    CFPreferencesSetAppValue(key, numberRef, applicationID);
    CFRelease(numberRef);

    key = CFSTR(KEY_SIXTEENBITCOLOR);
    CFPreferencesSetAppValue(key, m_dataBase->restore(KEY_SIXTEENBITCOLOR)->getBoolValue() ? yes : no, applicationID);

    CFPreferencesAppSynchronize(applicationID);
}

void PreferencesDialog::backupDataBase()
{
    if (!m_dataBase)
        return;

    if (m_dataBaseCopy)
        delete m_dataBaseCopy;

    m_dataBaseCopy = new TrackerSettingsDatabase(*m_dataBase);
}

void PreferencesDialog::restoreDataBase()
{
    if (!m_dataBaseCopy)
        return;

    if (m_dataBase)
        delete m_dataBase;

    m_dataBase = new TrackerSettingsDatabase(*m_dataBaseCopy);
}

void PreferencesDialog::updateControls()
{
    if (NULL == m_dataBase)
        return;

    ControlHandle checkBoxControl, control;
    ControlID checkBoxControlID[] = { { kAppSignature, 129 }, { kAppSignature, 128 }, { kAppSignature, 127 },
                                      { kAppSignature, 126 }, { kAppSignature, 125 }, { kAppSignature, 124 },
                                      { kAppSignature, 131 }, { kAppSignature, 132 } };

    GetControlByID(preferencesWindow, &checkBoxControlID[0], &checkBoxControl);
    bool b = false;

    if ((midiin && !midiin->getPortCount()) || !midiin)
        DisableControl(checkBoxControl);
    else
    {
        EnableControl(checkBoxControl);
        b = m_dataBase->restore(KEY_USEMIDI)->getBoolValue();
        SetControl32BitValue(checkBoxControl, b ? TRUE : FALSE);
    }

    GetControlByID(preferencesWindow, &checkBoxControlID[1], &checkBoxControl);
    b = m_dataBase->restore(KEY_SAVEPREFS)->getBoolValue();
    SetControl32BitValue(checkBoxControl, b ? TRUE : FALSE);

    GetControlByID(preferencesWindow, &checkBoxControlID[2], &checkBoxControl);
    b = false;

    if ((midiin && !midiin->getPortCount()) || !midiin)
    {
        // Disable checkbox
        DisableControl(checkBoxControl);

        // disable min. value text for slider
        GetControlByID(preferencesWindow, &checkBoxControlID[3], &control);
        DisableControl(control);

        // disable slider
        GetControlByID(preferencesWindow, &checkBoxControlID[4], &control);
        DisableControl(control);

        // disable max. value text for slider
        GetControlByID(preferencesWindow, &checkBoxControlID[5], &control);
        DisableControl(control);
    }
    else
    {
        EnableControl(checkBoxControl);
        b = m_dataBase->restore(KEY_RECORDVELOCITY)->getBoolValue();
        SetControl32BitValue(checkBoxControl, b ? TRUE : FALSE);

        // enable min. value text for slider
        GetControlByID(preferencesWindow, &checkBoxControlID[3], &control);
        EnableControl(control);

        // enable slider
        GetControlByID(preferencesWindow, &checkBoxControlID[4], &control);
        EnableControl(control);

        // enable max. value text for slider
        GetControlByID(preferencesWindow, &checkBoxControlID[5], &control);
        EnableControl(control);
    }

    GetControlByID(preferencesWindow, &checkBoxControlID[6], &checkBoxControl);
    UInt32 value = m_dataBase->restore(KEY_INSERTEMULATION)->getIntValue();
    SetControl32BitValue(checkBoxControl, value + 1);

    GetControlByID(preferencesWindow, &checkBoxControlID[7], &checkBoxControl);
    b = m_dataBase->restore(KEY_SIXTEENBITCOLOR)->getBoolValue();
    SetControl32BitValue(checkBoxControl, b ? TRUE : FALSE);
}

void PreferencesDialog::initDialog()
{
    ControlHandle popupButtonControl;
    ControlID popupButtonControlID = { kAppSignature, 130 };
    MenuRef menu;

    GetControlByID(preferencesWindow, &popupButtonControlID, &popupButtonControl);
    GetControlData(popupButtonControl, kControlEntireControl, kControlPopupButtonMenuRefTag, sizeof(menu), &menu, NULL);

    unsigned int nPorts = midiin ? midiin->getPortCount() : 0;

    if (nPorts)
    {
        UInt32 numItems = CountMenuItems(menu);
        DeleteMenuItems(menu, 1, numItems);

        // Check inputs.
        std::string portName;
        unsigned int nSelectedDevice = 0;
        for (unsigned int i = 0; i < nPorts; i++)
        {
            try
            {
                portName = midiin->getPortName(i);
            }
            catch (RtError& error)
            {
                error.printMessage();
            }

            if (strcmp(portName.c_str(), m_dataBase->restore(KEY_MIDIDEVICE)->getStringValue()) == 0)
                nSelectedDevice = i;

            CFStringRef CFStrPortName = CFStringCreateWithCString(NULL, portName.c_str(), kCFStringEncodingASCII);
            AppendMenuItemTextWithCFString(menu, CFStrPortName, 0, kMIDIDeviceBaseCommand + i, 0);
            CFRelease(CFStrPortName);

        }

        /*AppendMenuItemTextWithCFString(menu, CFSTR("Temp1"), 0, kMIDIDeviceBaseCommand + 1, 0);
           AppendMenuItemTextWithCFString(menu, CFSTR("Temp2"), 0, kMIDIDeviceBaseCommand + 2, 0);
           nPorts+=2;*/

        SetControlMaximum(popupButtonControl, nPorts);

        SetControlValue(popupButtonControl, nSelectedDevice + 1);

        storeMidiDeviceName(nSelectedDevice);

        EnableControl(popupButtonControl);
    }
    else
    {
        DisableControl(popupButtonControl);
    }

    updateControls();

    updateSliderVelocityAmplify();
}

UInt32 PreferencesDialog::getComboSelection()
{
    ControlHandle popupButtonControl;
    ControlID popupButtonControlID = { kAppSignature, 130 };

    GetControlByID(preferencesWindow, &popupButtonControlID, &popupButtonControl);
    return (unsigned int)GetControlValue(popupButtonControl) - 1;
}

void PreferencesDialog::toggleRecordVelocity()
{
    if (m_dataBase)
    {
        int i = m_dataBase->restore(KEY_RECORDVELOCITY)->getIntValue();
        m_dataBase->restore(KEY_RECORDVELOCITY)->store(!i);
        updateControls();
    }
}

void PreferencesDialog::toggleUseMidiDevice()
{
    if (m_dataBase)
    {
        int i = m_dataBase->restore(KEY_USEMIDI)->getIntValue();
        m_dataBase->restore(KEY_USEMIDI)->store(!i);
        updateControls();
    }
}

void PreferencesDialog::toggleSavePreferences()
{
    if (m_dataBase)
    {
        int i = m_dataBase->restore(KEY_SAVEPREFS)->getIntValue();
        m_dataBase->restore(KEY_SAVEPREFS)->store(!i);
        updateControls();
    }
}

void PreferencesDialog::updateSliderVelocityAmplify()
{
    ControlHandle control, controlSlider;
    ControlID controlID1 = { kAppSignature, 127 };
    ControlID controlID2 = { kAppSignature, 125 };
    GetControlByID(preferencesWindow, &controlID1, &control);
    GetControlByID(preferencesWindow, &controlID2, &controlSlider);

    char buffer[1024];
    sprintf(buffer, "Record velocity (amplify: %i%%)", m_dataBase->restore(KEY_VELOCITYAMPLIFY)->getIntValue());

    CFStringRef CFStrVelocityAmplify = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingASCII);
    SetControlTitleWithCFString(control, CFStrVelocityAmplify);
    CFRelease(CFStrVelocityAmplify);

    SetControlValue(controlSlider, m_dataBase->restore(KEY_VELOCITYAMPLIFY)->getIntValue());
}

void PreferencesDialog::storeMidiDeviceName(UInt32 deviceID)
{
    if (midiin && m_dataBase && deviceID < getNumMidiDevices())
    {
        try
        {
            std::string portName = midiin->getPortName(deviceID);
            m_dataBase->restore(KEY_MIDIDEVICE)->store(portName.c_str());
        }
        catch (RtError& error)
        {
            error.printMessage();
        }
    }
}

void PreferencesDialog::storeVelocityAmplify(UInt32 amplify)
{
    if (m_dataBase)
    {
        m_dataBase->restore(KEY_VELOCITYAMPLIFY)->store(amplify);
        updateSliderVelocityAmplify();
    }
}

void PreferencesDialog::setFakeInsertKey(UInt32 style)
{
    if (m_dataBase)
    {
        m_dataBase->restore(KEY_INSERTEMULATION)->store(style);
        updateControls();
    }
}

void PreferencesDialog::toggleUse15BitColorDepth()
{
    if (m_dataBase)
    {
        int i = m_dataBase->restore(KEY_SIXTEENBITCOLOR)->getIntValue();
        m_dataBase->restore(KEY_SIXTEENBITCOLOR)->store(!i);
        updateControls();
    }
}

UInt32 PreferencesDialog::getMidiDevIDFromString(const char* string)
{
    UInt32 nSelectedDevice = (unsigned)-1;

    if (!midiin)
        return nSelectedDevice;

    std::string portName;
    for (unsigned int i = 0; i < getNumMidiDevices(); i++)
    {
        try
        {
            portName = midiin->getPortName(i);
        }
        catch (RtError& error)
        {
            error.printMessage();
        }

        if (strcmp(portName.c_str(), string) == 0)
            nSelectedDevice = i;
    }

    return nSelectedDevice;
}

UInt32 PreferencesDialog::getNumMidiDevices()
{
    return midiin ? midiin->getPortCount() : 0;
}

UInt32 PreferencesDialog::getSelectedMidiDeviceID()
{
    if (m_dataBase)
    {
        return getMidiDevIDFromString(m_dataBase->restore(KEY_MIDIDEVICE)->getStringValue());
    }

    return (unsigned)-1;
}

bool PreferencesDialog::getUseMidiDeviceFlag()
{
    if (m_dataBase)
    {
        return m_dataBase->restore(KEY_USEMIDI)->getBoolValue();
    }

    return false;
}

bool PreferencesDialog::getRecordVelocityFlag()
{
    if (m_dataBase)
    {
        return m_dataBase->restore(KEY_RECORDVELOCITY)->getBoolValue();
    }

    return false;
}

UInt32 PreferencesDialog::getVelocityAmplify()
{
    if (m_dataBase)
    {
        return m_dataBase->restore(KEY_VELOCITYAMPLIFY)->getIntValue();
    }

    return 100;
}

UInt32 PreferencesDialog::getFakeInsertKey()
{
    if (m_dataBase)
    {
        return m_dataBase->restore(KEY_INSERTEMULATION)->getIntValue();
    }

    return 0;
}

bool PreferencesDialog::getUse15BitColorDepth()
{
    if (m_dataBase)
    {
        return m_dataBase->restore(KEY_SIXTEENBITCOLOR)->getBoolValue();
    }

    return false;
}